﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  public abstract class SimpleAsyncIndexDataSourceProvider : AsyncIndexDataSourceProvider
  {
    #region Static Fields

    private static readonly Guid ExecuteDataQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteCountSingleGroupQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteFindQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteGroupCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteSingleGroupCountQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteStatQueryCallId = DataUtils.CreateCallId();
    private static readonly Guid ExecuteSingleGroupStatQueryCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    protected SimpleAsyncIndexDataSourceProvider()
      : base()
    {
      this.InitializeFeatures();
    }

    #endregion

    #region CanFilterGroup Property

    public override bool CanFilterGroup
    {
      get
      {
        return m_canFilterGroup;
      }
    }

    private bool m_canFilterGroup; //false

    #endregion

    #region CanFind Property

    public override bool CanFind
    {
      get
      {
        return m_canFind;
      }
    }

    private bool m_canFind; //false

    #endregion

    #region CanGroupCount Property

    public override bool CanGroupCount
    {
      get
      {
        return m_canGroupCount;
      }
    }

    private bool m_canGroupCount; //false

    #endregion

    #region CanCalculateStats Property

    public override bool CanCalculateStats
    {
      get
      {
        return m_canCalculateStats;
      }
    }

    private bool m_canCalculateStats; //false

    #endregion

    protected abstract IAsyncResult BeginCount( AsyncCallback callback, object state );

    protected abstract long EndCount( IAsyncResult asyncResult );

    protected virtual IAsyncResult BeginCountSingleGroup(
      IEnumerable<FieldValue> fieldValues,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual long EndCountSingleGroup( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected abstract IAsyncResult BeginGetRange(
      long index,
      int count,
      AsyncCallback callback,
      object state );

    protected abstract IEnumerable EndGetRange( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginGetSortedRange(
      IEnumerable<SortDescription> sortOrder,
      long index,
      int count,
      AsyncCallback callback,
      object state );

    protected abstract IEnumerable EndGetSortedRange( IAsyncResult asyncResult );

    protected virtual IAsyncResult BeginGetGroupCount(
      IEnumerable<SortDescription> sortOrder,
      long index,
      int count,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual IEnumerable<long> EndGetGroupCount( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected virtual IAsyncResult BeginGetSingleGroupCount(
      IEnumerable<SortDescription> sortOrder,
      IEnumerable<FieldValue> fieldValues,
      long index,
      int count,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual IEnumerable<long> EndGetSingleGroupCount( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected virtual IAsyncResult BeginGetStatResult(
      StatFunction statFunction,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual StatResult EndGetStatResult( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected virtual IAsyncResult BeginGetSingleGroupStatResult(
      StatFunction statFunction,
      IEnumerable<FieldValue> fieldValues,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual StatResult EndGetSingleGroupStatResult( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected virtual IAsyncResult BeginFind(
      IEnumerable<SortDescription> sortOrder,
      IEnumerable<FindValue> fieldValues,
      FindPosition position,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual FindResult EndFind( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected sealed override IAsyncResult BeginExecuteCountQuery(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult;

      if( queryParameters.FilterGroup != null )
      {
        IEnumerable<FieldValue> fieldValues = this.GetFieldValues(
                                                queryParameters.Sort.SortDescriptions,
                                                queryParameters.FilterGroup.Values ).ToList();

        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteCountSingleGroupQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginCountSingleGroup( fieldValues, cb, st ),
          null );
      }
      else
      {
        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteCountQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginCount( cb, st ),
          null );
      }

      return asyncResult;
    }

    protected sealed override CountQueryResult EndExecuteCountQuery( IAsyncResult asyncResult )
    {
      long count;
      IAsyncResult childResult = asyncResult.GetLastAsyncResult<IAsyncResult>();

      if( asyncResult.UseCallId( SimpleAsyncIndexDataSourceProvider.ExecuteCountSingleGroupQueryCallId ) )
      {
        count = this.EndCountSingleGroup( childResult );
      }
      else
      {
        asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteCountQueryCallId );

        count = this.EndCount( childResult );
      }

      return new CountQueryResult( count );
    }

    protected sealed override IAsyncResult BeginExecuteDataQuery(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      // Retrieve the query parameters.
      IEnumerable<SortDescription> sortOrder = ( queryParameters.Sort != null ) ? queryParameters.Sort.SortDescriptions : null;
      long index = ( queryParameters.Skip != null ) ? queryParameters.Skip.Count : 0;
      int count = ( queryParameters.Take != null ) ? queryParameters.Take.Count : int.MaxValue;

      // Check out if a sort order was specified.
      bool useDefaultSortOrder = ( sortOrder == null );

      // Retrieve the appropriate method to call to execute the query.
      Func<AsyncCallback, object, IAsyncResult> beginGetRange;
      if( useDefaultSortOrder )
      {
        beginGetRange = ( cb, st ) => this.BeginGetRange( index, count, cb, st );
      }
      else
      {
        beginGetRange = ( cb, st ) => this.BeginGetSortedRange( sortOrder, index, count, cb, st );
      }

      ContextAsyncResult asyncResult = new ContextAsyncResult(
                                         SimpleAsyncIndexDataSourceProvider.ExecuteDataQueryCallId,
                                         callback,
                                         state );
      // Set the context in order to call the appropriate method to retrieve the result.
      asyncResult.CallContext = useDefaultSortOrder;

      asyncResult.Start( beginGetRange, null );

      return asyncResult;
    }

    protected sealed override DataQueryResult EndExecuteDataQuery( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteDataQueryCallId );

      ContextAsyncResult contextAsyncResult = ( ContextAsyncResult )asyncResult;
      IAsyncResult childAsyncResult = contextAsyncResult.LastIAsyncResult;
      bool useDefaultSortOrder = ( bool )contextAsyncResult.CallContext;
      IEnumerable dataList;

      if( useDefaultSortOrder )
      {
        dataList = this.EndGetRange( childAsyncResult );
      }
      else
      {
        dataList = this.EndGetSortedRange( childAsyncResult );
      }

      return new DataQueryResult( dataList );
    }

    protected sealed override IAsyncResult BeginExecuteGroupCountQuery(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult;

      IEnumerable<SortDescription> sortOrder = queryParameters.Sort.SortDescriptions;
      long index = ( queryParameters.Skip != null ) ? queryParameters.Skip.Count : 0;
      int length = queryParameters.Take.Count;

      if( queryParameters.FilterGroup != null )
      {
        IEnumerable<FieldValue> fieldValues = this.GetFieldValues(
                                                queryParameters.Sort.SortDescriptions,
                                                queryParameters.FilterGroup.Values ).ToList();

        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteSingleGroupCountQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginGetSingleGroupCount( sortOrder, fieldValues, index, length, cb, st ),
          null );
      }
      else
      {
        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteGroupCountQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginGetGroupCount( sortOrder, index, length, cb, st ),
          null );
      }

      return asyncResult;
    }

    protected sealed override GroupCountQueryResult EndExecuteGroupCountQuery( IAsyncResult asyncResult )
    {
      IEnumerable<long> groupCount;
      IAsyncResult childResult = asyncResult.GetLastAsyncResult<IAsyncResult>();

      if( asyncResult.UseCallId( SimpleAsyncIndexDataSourceProvider.ExecuteSingleGroupCountQueryCallId ) )
      {
        groupCount = this.EndGetSingleGroupCount( childResult );
      }
      else
      {
        asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteGroupCountQueryCallId );

        groupCount = this.EndGetGroupCount( childResult );
      }

      return new GroupCountQueryResult( groupCount );
    }

    protected sealed override IAsyncResult BeginExecuteStatQuery(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      StatFunction statFunction = queryParameters.Function.StatFunction;
      AsyncResult asyncResult;

      if( queryParameters.FilterGroup != null )
      {
        IEnumerable<FieldValue> fieldValues = this.GetFieldValues(
                                                queryParameters.Sort.SortDescriptions,
                                                queryParameters.FilterGroup.Values ).ToList();

        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteSingleGroupStatQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginGetSingleGroupStatResult( statFunction, fieldValues, cb, st ),
          null );
      }
      else
      {
        asyncResult = new AsyncResult( SimpleAsyncIndexDataSourceProvider.ExecuteStatQueryCallId, callback, state );
        asyncResult.Start(
          ( cb, st ) => this.BeginGetStatResult( statFunction, cb, st ),
          null );
      }

      return asyncResult;
    }

    protected sealed override StatQueryResult EndExecuteStatQuery( IAsyncResult asyncResult )
    {
      StatResult result;
      IAsyncResult childResult = asyncResult.GetLastAsyncResult<IAsyncResult>();

      if( asyncResult.UseCallId( SimpleAsyncIndexDataSourceProvider.ExecuteSingleGroupStatQueryCallId ) )
      {
        result = this.EndGetSingleGroupStatResult( childResult );
      }
      else
      {
        asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteStatQueryCallId );

        result = this.EndGetStatResult( childResult );
      }

      return new StatQueryResult( result );
    }

    protected sealed override IAsyncResult BeginExecuteFindQuery(
      FindQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      Debug.Assert( ( queryParameters.Sort != null ) && ( queryParameters.Find != null ) );

      // Retrieve the query parameters.
      IEnumerable<SortDescription> sortOrder = queryParameters.Sort.SortDescriptions;
      IEnumerable<FindValue> fieldValues = queryParameters.Find.Values;
      FindPosition position = queryParameters.Find.Position;

      AsyncResult asyncResult = new AsyncResult(
                                  SimpleAsyncIndexDataSourceProvider.ExecuteFindQueryCallId,
                                  callback,
                                  state );

      asyncResult.Start(
        ( cb, st ) => this.BeginFind( sortOrder, fieldValues, position, cb, st ),
        null );

      return asyncResult;
    }

    protected sealed override FindQueryResult EndExecuteFindQuery( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( SimpleAsyncIndexDataSourceProvider.ExecuteFindQueryCallId );

      IAsyncResult childAsyncResult = asyncResult.GetLastAsyncResult<IAsyncResult>();
      FindResult result = this.EndFind( childAsyncResult );

      return new FindQueryResult( result );
    }

    private IEnumerable<FieldValue> GetFieldValues(
      IEnumerable<SortDescription> sortOrder,
      IEnumerable<FilterGroupValue> values )
    {
      return sortOrder.Zip( values, ( sort, value ) => new FieldValue( sort.PropertyName, value.Value ) );
    }

    private void InitializeFeatures()
    {
      Type dataSourceProviderType = this.GetType();
      bool groupLevelCountImplemented = DataUtils.IsMethodImplemented(
                                          DataUtils.GetMethodDefinition<IEnumerable<FieldValue>, AsyncCallback, object, IAsyncResult>(
                                            ( fieldValues, callback, state ) => this.BeginCountSingleGroup( fieldValues, callback, state ) ),
                                          dataSourceProviderType );
      bool allLevelGroupCountImplemented = DataUtils.IsMethodImplemented(
                                             DataUtils.GetMethodDefinition<IEnumerable<SortDescription>, long, int, AsyncCallback, object, IAsyncResult>(
                                               ( sortOrder, index, count, callback, state ) => this.BeginGetGroupCount( sortOrder, index, count, callback, state ) ),
                                             dataSourceProviderType );
      bool groupLevelGroupCountImplemented = DataUtils.IsMethodImplemented(
                                               DataUtils.GetMethodDefinition<IEnumerable<SortDescription>, IEnumerable<FieldValue>, long, int, AsyncCallback, object, IAsyncResult>(
                                                 ( sortOrder, fieldValues, index, count, callback, state ) => this.BeginGetSingleGroupCount( sortOrder, fieldValues, index, count, callback, state ) ),
                                               dataSourceProviderType );
      bool allLevelStatImplemented = DataUtils.IsMethodImplemented(
                                       DataUtils.GetMethodDefinition<StatFunction, AsyncCallback, object, IAsyncResult>(
                                         ( statFunction, callback, state ) => this.BeginGetStatResult( statFunction, callback, state ) ),
                                       dataSourceProviderType );
      bool groupLevelStatImplemented = DataUtils.IsMethodImplemented(
                                         DataUtils.GetMethodDefinition<StatFunction, IEnumerable<FieldValue>, AsyncCallback, object, IAsyncResult>(
                                           ( statFunction, fieldValues, callback, state ) => this.BeginGetSingleGroupStatResult( statFunction, fieldValues, callback, state ) ),
                                         dataSourceProviderType );
      bool findImplemented = DataUtils.IsMethodImplemented(
                               DataUtils.GetMethodDefinition<IEnumerable<SortDescription>, IEnumerable<FindValue>, FindPosition, AsyncCallback, object, IAsyncResult>(
                                 ( sortOrder, findValues, position, callback, state ) => this.BeginFind( sortOrder, findValues, position, callback, state ) ),
                               dataSourceProviderType );

      m_canFind = findImplemented;
      m_canGroupCount = m_canFind && allLevelGroupCountImplemented;
      m_canCalculateStats = allLevelStatImplemented;

      m_canFilterGroup = ( groupLevelCountImplemented )
                      && ( !m_canGroupCount || groupLevelGroupCountImplemented )
                      && ( !m_canCalculateStats || groupLevelStatImplemented );
    }
  }
}
